/*
  The tuioZone library provides a way to set zones (or generic objects) within a screen to respond in useful ways to TUIO events.

  (c) copyright

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public
  License Version 3 as published by the Free Software Foundation.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
 */

package tuioZones;

import processing.core.*;
import oscP5.*;
import java.lang.reflect.Method;
import netbeans_processing_template.Applet;

/**
 * The TUIOzoneCollection class provides a way to define zones that respond to
 * TUIO events in unique ways. It handles the processing of TUIO events and
 * provides methods to get data about each zone and related TUIO events.
 *
 * @example _tzDemo1
 * @author jLyst
 *
 */
public class TUIOzoneCollection implements PConstants {

	Applet myParent;
	Method hSwipeMethod;
	Method tReleaseMethod;

	OscP5 oscP5;
	TouchPoint tpoint[];// touch point array
	TouchZone tzone[];// touch zone array
	int maxPoints = 10;
	int maxZones = 10;

	final String VERSION = "0.1.0";

	double rotate;
	float[] translation = new float[2];	

	/**
	 * The Constructor initializes a collection of zones that respond to TUIO
	 * cursor actions. A default zone, named 'canvas', is set that spans the
	 * entire width and height of the sketch. Port 3333 is used by default to
	 * receive TUIO messages
	 *
	 * @param theParent
	 */
	public TUIOzoneCollection(Applet theParent) {
		this(theParent, 3333);
	}

	/**
	 * The Constructor initializes a collection of zones that respond to TUIO
	 * cursor actions. A default zone, named 'canvas', is set that spans the
	 * entire width and height of the sketch.
	 *
	 * @param theParent
	 * @param port
	 *            the port where TUIO messages will be sent to. (for example,
	 *            3333)
	 */
	public TUIOzoneCollection(Applet theParent, int port) {
		myParent = theParent;
		oscP5 = new OscP5(this, port);
		tpoint = new TouchPoint[maxPoints];
		tzone = new TouchZone[maxZones];
		tzone[0] = new TouchZone("canvas", 0, 0, myParent.width,
				myParent.height);
		try {
			hSwipeMethod = myParent.getClass().getMethod("hSwipeEvent",
					new Class[] { String.class });
		} catch (Exception e) {
			// no such method, or an error.. which is fine, just ignore
		}
		try {
			tReleaseMethod = myParent.getClass().getMethod("tReleaseEvent",
					new Class[] { int.class });
		} catch (Exception e) {
			// no such method, or an error.. which is fine, just ignore
		}
		myParent.registerPre(this);
	}

	/**
	 * return the version of the library.
	 *
	 * @return String
	 */
	public String version() {
		return VERSION;
	}

	// /////////////////////////////////////////////////////////////
	// SET METHODS//////////////////////////////////////////////////
	// /////////////////////////////////////////////////////////////
	/**
	 * Define a new circular zone that will respond to TUIO events in a unique
	 * way.
	 *
	 * @param zNameIn
	 *            a name for the zone
	 * @param xIn
	 *            the x-coordinate of the center of the circular zone
	 * @param yIn
	 *            the y-coordinate of the center of the circular zone
	 * @param rIn
	 *            the radius of the circular zone
	 */
	public void setZone(String zNameIn, int xIn, int yIn, int rIn) { // like a
		// constructor
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] == null) {
				tzone[i] = new TouchZone(zNameIn, xIn, yIn, rIn);
				break;
			}
		}
	}

	/**
	 * Define a new rectangular zone that will respond to TUIO events in a
	 * unique way.
	 *
	 * @param zNameIn
	 *            a name for the zone
	 * @param xIn
	 *            the x-coordinate of the upper left corner of the zone
	 * @param yIn
	 *            the y-coordinate of the upper left corner of the zone
	 * @param wIn
	 *            the width of the zone
	 * @param hIn
	 *            the height of the zone
	 */
	public void setZone(String zNameIn, int xIn, int yIn, int wIn, int hIn) {// like
		// a
		// constructor
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] == null) {
				tzone[i] = new TouchZone(zNameIn, xIn, yIn, wIn, hIn);
				break;
			}
		}
	}

	/**
	 * Modify rectangular zone primitive data.
	 *
	 * @param zNameIn
	 *            the name of the zone to be modified
	 * @param xIn
	 *            the x-coordinate of the upper left corner of the zone
	 * @param yIn
	 *            the y-coordinate of the upper left corner of the zone
	 * @param wIn
	 *            the width of the zone
	 * @param hIn
	 *            the height of the zone
	 */
	public void setZoneData(String zNameIn, int xIn, int yIn, int wIn, int hIn) {
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].name.equals(zNameIn)) {
					tzone[i].x = xIn;
					tzone[i].y = yIn;
					tzone[i].w = wIn;
					tzone[i].h = hIn;
				}
			}
		}
	}

	/**
	 * Set zone parameters, like 'DRAGGABLE', 'SCALABLE', or 'HSWIPEABLE'.
	 *
	 * @param zNameIn
	 *            the name of the zone to be modified
	 * @param paramName
	 *            the name of the parameter to be set. Parameters names can be
	 *            'DRAGGABLE', 'SCALABLE' or 'HSWIPEABLE'. Note, 'HSWIPEABLE'
	 *            can only be applied to rectangle zones.
	 * @param bool
	 *            Set the parameter to be true or false.
	 * @example _tzDrag
	 * @example _tzThrow
	 * @example _tzSwipe
	 */
	public void setZoneParameter(String zNameIn, String paramName, boolean bool) {
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].name.equals(zNameIn)) {
					if (paramName.equals("DRAGGABLE"))
						tzone[i].draggable = bool;
					else if (paramName.equals("SCALABLE"))
						tzone[i].scalable = bool;
					else if (paramName.equals("HSWIPEABLE"))
						tzone[i].hSwipeable = bool;
					else if (paramName.equals("THROWABLE"))
						tzone[i].throwable = bool;
					else if (paramName.equals("ROTATABLE"))
						tzone[i].rotatable = bool;
					break;
				}
			}
		}
	}

	/**
	 * Kill a zone.
	 *
	 * @param zNameIn
	 *            the name of the zone to be killed.
	 *
	 */
	public void killZone(String zNameIn) {
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].name.equals(zNameIn)) {
					tzone[i] = null;
					break;
				}
			}
		}
	}

	// /////////////////////////////////////////////////////////////
	// INVOKE EVENT METHODS//////////////////////////////////////////////////
	// /////////////////////////////////////////////////////////////

	public void makeHswipeEvent(String zName) {
		if (hSwipeMethod != null) {
			try {
				hSwipeMethod.invoke(myParent, zName);
			} catch (Exception e) {
				System.err
						.println("Disabling hSwipeEvent() for * because of an error.");

				e.printStackTrace();
				hSwipeMethod = null;
			}
		}
	}

	public void touchReleased(int index) {
		if (tReleaseMethod != null) {
			try {
				tReleaseMethod.invoke(myParent, index);
			} catch (Exception e) {
				System.err
						.println("Disabling hSwipeEvent() for * because of an error.");

				e.printStackTrace();
				tReleaseMethod = null;
			}
		}
	}

	// /////////////////////////////////////////////////////////////
	// GET METHODS//////////////////////////////////////////////////
	// /////////////////////////////////////////////////////////////
	/**
	 * Get the zone x-coordinate. Upper left corner for rectangle. Center for
	 * circle.
	 *
	 * @param zNameIn
	 *            the name of the zone.
	 * @return an integer
	 *
	 */
	public int getZoneX(String zNameIn) {
		int x = -999, i = getZoneIndex(zNameIn);
		if (i != -999) {
			x = tzone[i].x;
		}
		return x;
	}

	/**
	 * Get the zone y-coordinate. Upper left corner for rectangle. Center for
	 * circle.
	 *
	 * @param zNameIn
	 *            the name of the zone.
	 * @return an integer
	 *
	 */
	public int getZoneY(String zNameIn) {
		int y = -999, i = getZoneIndex(zNameIn);
		if (i != -999) {
			y = tzone[i].y;
		}
		return y;
	}

	/**
	 * Get the zone scale factor.
	 *
	 * @param zNameIn
	 *            the name of the zone.
	 * @return a float
	 *
	 */
	public float getZoneScale(String zNameIn) {
		float scl = 1;
		int i = getZoneIndex(zNameIn);
		if (i != -999) {
			scl = tzone[i].scl;
		}
		return scl;
	}

	/**
	 * Get the zone width.
	 *
	 * @param zNameIn
	 *            the name of the zone.
	 * @return an integer
	 *
	 */
	public int getZoneWidth(String zNameIn) {
		int width = -999, i = getZoneIndex(zNameIn);
		if (i != -999) {
			width = tzone[i].w;
		}
		return width;
	}

	/**
	 * Get the zone height.
	 *
	 * @param zNameIn
	 *            the name of the zone.
	 * @return an integer
	 *
	 */
	public int getZoneHeight(String zNameIn) {
		int height = -999, i = getZoneIndex(zNameIn);
		if (i != -999) {
			height = tzone[i].h;
		}
		return height;
	}

	/**
	 * Get zone data.
	 *
	 * <p>
	 * 0-the x-coordinate of the zone (upper left for rectangles and center for
	 * circles)
	 * <p>
	 * 1-the x-coordinate of the zone (upper left for rectangles and center for
	 * circles)
	 * <p>
	 * 2-the width of the zone (the radius if circle)
	 * <p>
	 * 3-the height of the zone (the radius if circle)
	 * <p>
	 * 4-the primary touch point x-coordinate (-999 if the zone is not being
	 * touched)
	 * <p>
	 * 5-the primary touch point y-coordinate (-999 if the zone is not being
	 * touched)
	 * <p>
	 * 6-the primary touch point index or ID (for use in getTrail method, -999
	 * if none exist)
	 *
	 * @param zNameIn
	 *            the name of the zone.
	 * @return Array of zone data as integers
	 *
	 *
	 */
	public int[] getZoneData(String zNameIn) {
		int[] data = new int[7]; // x y w h primaryX primaryY primaryPointIndex
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].name.equals(zNameIn)) {
					int ppi = tzone[i].primePoint;// primary point index
					data[0] = tzone[i].x;
					data[1] = tzone[i].y;
					data[2] = tzone[i].w;
					data[3] = tzone[i].h;
					if (ppi > -1) {
						data[4] = tpoint[ppi].coord[tpoint[ppi].coord.length - 1][0];
						data[5] = tpoint[ppi].coord[tpoint[ppi].coord.length - 1][1];
					} else {
						data[4] = data[5] = -999;
					}
					data[6] = tzone[i].primePoint;
					break;
				}
			}
		}
		return data;
	}

	/**
	 * Determine if the zone is being touched.
	 *
	 * @param zNameIn
	 *            the name of the zone.
	 * @return a boolean
	 * @example _tzPressed
	 */
	public boolean isZonePressed(String zNameIn) {
		boolean pressed = false;
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].name.equals(zNameIn) && tzone[i].primePoint > -1)
					pressed = true;
			}
		}
		return pressed;
	}

	/**
	 * Determine the toggle state of the zone. Default is false.
	 *
	 * @param zNameIn
	 *            the name of the zone.
	 * @return a boolean
	 * @example _tzToggle
	 */
	public boolean isZoneToggleOn(String zNameIn) {
		boolean pressed = false;
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].name.equals(zNameIn) && tzone[i].touches % 2 == 1)
					pressed = true;
			}
		}
		return pressed;
	}

	/**
	 * Get coordinates and touch ID of all the touch points (TUIO cursors) on
	 * the screen.
	 * <p>
	 * [i][0] - x coordinate
	 * <p>
	 * [i][1] - y coordinate
	 * <p>
	 * [i][2] - ID (Useful for getTrail method)
	 *
	 *
	 * @return double integer array
	 *
	 */
	public int[][] getPoints() {
		int[][] coord = new int[0][3];
		for (int i = 0; i < tpoint.length; i++) {
			if (tpoint[i] != null) {
				int[] toAppend = {
						tpoint[i].coord[tpoint[i].coord.length - 1][0],
						tpoint[i].coord[tpoint[i].coord.length - 1][1],
						tpoint[i].touchID };
				coord = (int[][]) PApplet.append(coord, toAppend);
			}
		}
		return coord;
	}

	/**
	 * Get a trail of points left by a touch.
	 * <p>
	 * [i][0] - x coordinate
	 * <p>
	 * [i][1] - y coordinate
	 *
	 * @param index
	 *            The index of the point trail.
	 * @return a double integer array of coordinates
	 *
	 */
	public int[][] getTrail(int index) {
		int[][] coord = new int[0][6];
		if (tpoint[index] != null) {
			coord = tpoint[index].coord;
		}
		return coord;
	}

	public float getRotationAngle() {
		return new Float(rotate);
	}

	public float[] getTranslation() {
		return translation;
	}

	// /////////////////////////////////////////////////////////////
	// HANDLE TUIO EVENTS///////////////////////////////////////////
	// /////////////////////////////////////////////////////////////

	void oscEvent(OscMessage theOscMessage) {
		// //////////////////////////////////////alive/////////////////////////////////////////////////////////////////////////
		if (theOscMessage.get(0).stringValue().equals("alive")) {
			int sid;
			boolean found;
			// cycle through points
			for (int i = 0; i < tpoint.length; i++) {
				if (tpoint[i] != null) {
					sid = tpoint[i].SID;
					found = false;
					// see if point is alive
					if (!found) {
						// cycle through alive sid's
						for (int j = 1; j < theOscMessage.arguments().length; j++) {
							if (theOscMessage.get(j).intValue() == sid) {
								found = true;
								break;
							}
						}
					}
					// if point is not alive, kill it
					if (!found) {
						tpoint[i] = null;
						touchReleased(i);
						removePrime(i);
					}
				}
			}
		}

		// //////////////////////////////////////modify or add////////////////
		if (theOscMessage.typetag().startsWith("sifffff") == true) {
			int sid = theOscMessage.get(1).intValue();
			int xIn = (int) (myParent.width * theOscMessage.get(2).floatValue());
			int yIn = (int) (myParent.height * theOscMessage.get(3)
					.floatValue());
			int vxIn = (int) (myParent.width * theOscMessage.get(4)
					.floatValue());
			int vyIn = (int) (myParent.height * theOscMessage.get(5)
					.floatValue());
			boolean modified = false;
			int index = -999;
			int zone = 0;
			String zName = "CANVAS";
			for (int i = 0; i < tpoint.length; i++) {
				if (tpoint[i] != null) {
					if (tpoint[i].SID == sid) {
						index = i;
						// calculate velocities until tbeta includes them in the
						// TUIO message
						if (vxIn == 0 && vyIn == 0) {
							int l = tpoint[i].coord.length - 1;
							int tIn = myParent.millis();
							vxIn = (int) (1000f * (xIn - tpoint[i].coord[l][0]) / (tIn - tpoint[i].coord[l][4]));
							vyIn = (int) (1000f * (yIn - tpoint[i].coord[l][1]) / (tIn - tpoint[i].coord[l][4]));
							break;
						}
					}
				}
			}
			// check zones **layers matter--last zone created is on top (wins)
			for (int i = 0; i < tzone.length; i++) {
				if (tzone[i] != null) {
					if (tzone[i].rectangle) {
						if (yIn > tzone[i].y && yIn < (tzone[i].y + tzone[i].h)) {
							// is it a horizontal swipe?
							if (index != -999 && tzone[i].hSwipeable) {
								// offset by 0.1 to avoid 0 delta (double swipe)
								float xcenter = tzone[i].cx + 0.1f;
								int lastX = tpoint[index].getLastPoint()[0];
								// event sent if the swipe crosses the vertical
								// centerline of the zone at a velocity of
								// 1 screen width per 1 second or faster
								if ((xIn - xcenter) * (lastX - xcenter) < 0
										&& PApplet.abs(vxIn) * 2 > myParent.width) {
									makeHswipeEvent(tzone[i].name);
								}
							}
							// is it within a zone?
							if (xIn > tzone[i].x
									&& xIn < (tzone[i].x + tzone[i].w)) {
								zName = tzone[i].name;
								zone = i;
							}
						}
					} else if (tzone[i].circle) {// TODO add swipeable code for
						// circle
						if (PApplet.dist(xIn, yIn, tzone[i].x, tzone[i].y) < tzone[i].r) {
							zName = tzone[i].name;
							zone = i;
						}
					}
				}
			}
			// update known point using sid
			if (index != -999) {
				tpoint[index].addPoint(sid, xIn, yIn, vxIn, vyIn, zone, index);
				modified = true;
			}
			// create new touch point if not found
			if (!modified) {
				for (int i = 0; i < tpoint.length; i++) {
					if (tpoint[i] == null) {
						tpoint[i] = new TouchPoint(sid, xIn, yIn, zone, zName,
								i);
						break;
					}
				}
			}
		}
	}

	// /////////////////////////////////////////////////////////////
	// PROCESS FUNCTIONS////////////////////////////////////////////
	// /////////////////////////////////////////////////////////////
	void removePrime(int tpointIn) {
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].primePoint == tpointIn) {
					tzone[i].primePoint = -999;
					tzone[i].releaseTime = myParent.millis();
					tzone[i].xi = tzone[i].x;// set intermediate memory
					tzone[i].yi = tzone[i].y;// set intermediate memory
					tzone[i].wi = tzone[i].w;// set intermediate memory
					tzone[i].hi = tzone[i].h;// set intermediate memory
					tzone[i].scli = tzone[i].scl;// set intermediate memory
				}
			}
		}
	}

	void modifyZone(int xIn, int yIn, int vxIn, int vyIn, int tpointIn, int zone) {
		int[] touches = new int[0];
		// COUNT TOUCHES
		for (int i = 0; i < tpoint.length; i++) {
			if (tpoint[i] != null) {
				if (tpoint[i].zone0 == zone) {
					touches = (int[]) PApplet.append(touches, i);
				}
			}
		}
		// IF DRAGGABLE
		if (tzone[zone].primePoint == tpointIn && tzone[zone].draggable
				&& touches.length == 1 && tpoint[tpointIn].scalar == false) {//
			tzone[zone].x = tzone[zone].xi
					+ (xIn - tpoint[tpointIn].coord[0][0]);
			tzone[zone].y = tzone[zone].yi
					+ (yIn - tpoint[tpointIn].coord[0][1]);
			tzone[zone].vx = (tzone[zone].vx + vxIn) / 2;
			tzone[zone].vy = (tzone[zone].vy + vyIn) / 2;
		}
		// IF SCALABLE
		/*if (touches.length == 2) {
			for (int i = 0; i < tpoint.length; i++) {
				if (tpoint[i] != null && tpointIn != i) {
					if (tpoint[i].zone0 == zone) {
						tpoint[i].scalar = true;// avoid jump after scaling
						tpoint[tpointIn].scalar = true;
						int[] iLast = tpoint[i].getLastPoint();
						int[] tpointInFirst = tpoint[tpointIn].getFirstPoint();
						int[] iFirst = tpoint[i].getFirstPoint();
						float scl = PApplet.dist(iLast[0], iLast[1], xIn, yIn)
								/ PApplet.dist(iFirst[0], iFirst[1],
										tpointInFirst[0], tpointInFirst[1]);
						// estimate 'center of mass' of scale gesture
						float iDelta = PApplet.dist(iLast[0], iLast[1],
								iFirst[0], iFirst[1]);
						float tpointInDelta = PApplet.dist(xIn, yIn,
								tpointInFirst[0], tpointInFirst[1]);
						float iRatio = 1.0f - iDelta / (iDelta + tpointInDelta);
						float tpointInRatio = 1.0f - tpointInDelta
								/ (iDelta + tpointInDelta);
						int midx = (int) (xIn * tpointInRatio + iLast[0]
								* iRatio);
						int midy = (int) (yIn * tpointInRatio + iLast[1]
								* iRatio);
						int newX = midx - (int) ((midx - tzone[zone].xi) * scl);
						int newY = midy - (int) ((midy - tzone[zone].yi) * scl);
						int newW = (int) (tzone[zone].wi * scl);
						int newH = (int) (tzone[zone].hi * scl);
						// modify zones
						tzone[zone].scl = scl * tzone[zone].scli;
						tzone[zone].cx = newX + newW / 2;
						tzone[zone].cx = newY + newH / 2;
						if (tzone[zone].scalable) {
							tzone[zone].x = newX;
							tzone[zone].y = newY;
							tzone[zone].w = newW;
							tzone[zone].h = newH;
						}
						break;
					}
				}
			}
		}*/
		// if ROTATABLE --- under construction
		if (tzone[zone].rotatable && touches.length == 2) {			
			tpoint[touches[0]].scalar = true;// avoid jump after scaling
			tpoint[touches[1]].scalar = true;

			int[] iFirst = tpoint[touches[0]].getFirstPoint();
			int[] iLast = tpoint[touches[0]].getLastPoint();
			int[] jFirst = tpoint[touches[1]].getFirstPoint();
			int[] jLast = tpoint[touches[1]].getLastPoint();

			myParent.translate(tzone[zone].w/2, tzone[zone].h/2);
			PApplet.println("here" + myParent.screenX(0, 0));

			PApplet.println("iFirst: "+iFirst[0]);
			PApplet.println("iLast: "+iLast[0]);
			PApplet.println("jFirst: "+jFirst[0]);
			PApplet.println("jLast: "+jLast[0]);

			float x1;
			float y1;
			float x2;
			float y2;

			if (iLast[0]<jLast[0]) {
				x1 = iLast[0];
				x2 = jLast[0];
			} else {
				x1 = jLast[0];
				x2 = iLast[0];
			}

			if (iLast[1]<jLast[1]) {
				y1 = iLast[1];
				y2 = jLast[1];
			} else {
				y1 = jLast[1];
				y2 = iLast[1];
			}

			float a = x2-x1;
			float b = y2-y1;
			double c = java.lang.Math.sqrt((a*a)-(b*b));
			this.rotate = java.lang.Math.cos(b/c);
            System.out.println(rotate);
			this.translation[0] = x2-((x2-x1)/2);
			this.translation[1] = y2-((y2-y1)/2);
			//myParent.pushMatrix();
			myParent.translate(this.translation[0],this.translation[1]);
			System.out.println("rotating");            
			//myParent.rotate(new Float(this.rotate));
			//myParent.popMatrix();
			tzone[zone].x = tzone[zone].xi;// + (xIn - tpoint[1].coord[0][0]);
			tzone[zone].y = tzone[zone].yi;// + (yIn - tpoint[1].coord[0][1]);
			//tzone[zone].vx = (tzone[zone].vx + vxIn) / 2;
			//tzone[zone].vy = (tzone[zone].vy + vyIn) / 2;
			/*if (tzone[zone].rotatable) {
				tzone[zone].x = tzone[zone].x;
				tzone[zone].y = tzone[zone].y;
				tzone[zone].w = tzone[zone].w;
				tzone[zone].h = tzone[zone].h;
			}*/
		}
		if (tzone[zone].circle) {
			tzone[zone].cx = tzone[zone].x;
			tzone[zone].cy = tzone[zone].y;
		}
		if (tzone[zone].rectangle) {
			tzone[zone].cx = tzone[zone].x + tzone[zone].w / 2;
			tzone[zone].cy = tzone[zone].y + tzone[zone].h / 2;
		}
	}

	// Give a zone a primary point
	void markZone(int tpointIn, int zone) {
		if (tzone[zone].primePoint == -999) {
			tzone[zone].primePoint = tpointIn;
			tzone[zone].touches++;
		}
	}

	// called by the processing applet at the beginning of each draw
	public void pre() {
		processZoneInertias();
	}

	void processZoneInertias() { // throwable objects
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].throwable && tzone[i].primePoint == -999
						&& (tzone[i].vx != 0 || tzone[i].vy != 0)) {
					float vratio = (float) tzone[i].vy / tzone[i].vx;
					if (tzone[i].vx > myParent.width) {
						tzone[i].vx = myParent.width;
						tzone[i].vy = (int) (vratio * myParent.width);
					}// governor
					if (tzone[i].vy > myParent.width) {
						tzone[i].vy = myParent.width;
						tzone[i].vx = (int) (myParent.width / vratio);
					}
					float ax = -5000, ay = -5000;// deceleration
					if (tzone[i].vy == 0) {
						ay = 0;
					} else {
						float ratio = (float) tzone[i].vx / tzone[i].vy;
						ay = -PApplet.sqrt(ax * ax / ((ratio * ratio) + 1))
								* PApplet.abs(tzone[i].vy) / tzone[i].vy;
						ax = ratio * ay;
					}
					float t = (myParent.millis() - tzone[i].releaseTime) / 1000f;
					if (PApplet.abs(ax * t) >= PApplet.abs(tzone[i].vx)
							|| PApplet.abs(ay * t) >= PApplet.abs(tzone[i].vy)
							|| tzone[i].cx < 0 || tzone[i].cx > myParent.width
							|| tzone[i].cy < 0 || tzone[i].cy > myParent.height) {// finish
						// movement
						ax = tzone[i].vx = 0;
						tzone[i].xi = tzone[i].x;
						ay = tzone[i].vy = 0;
						tzone[i].yi = tzone[i].y;
					} else {
						tzone[i].x = (int) (tzone[i].xi + tzone[i].vx * t + 0.5f
								* ax * PApplet.pow(t, 2));
						tzone[i].y = (int) (tzone[i].yi + tzone[i].vy * t + 0.5f
								* ay * PApplet.pow(t, 2));
					}
					if (tzone[i].circle) {
						tzone[i].cx = tzone[i].x;
						tzone[i].cy = tzone[i].y;
					}
					if (tzone[i].rectangle) {
						tzone[i].cx = tzone[i].x + tzone[i].w / 2;
						tzone[i].cy = tzone[i].y + tzone[i].h / 2;
					}

				}
			}
		}
	}

	// /////////////////////////////////////////////////////////////
	// HELPER METHODS//////////////////////////////////////////////////
	// /////////////////////////////////////////////////////////////

	int getZoneIndex(String zNameIn) {
		int index = -999;
		for (int i = 0; i < tzone.length; i++) {
			if (tzone[i] != null) {
				if (tzone[i].name.equals(zNameIn)) {
					index = i;
					break;
				}
			}
		}
		return index;
	}

	// /////////////////////////////////////////////////////////////
	// TOUCH POINT CLASS//////////////////////////////////////////////////
	// /////////////////////////////////////////////////////////////
	class TouchPoint {
		int coord[][];
		int SID, zone, zone0, touchID;
		String zName, zName0;
		boolean scalar = false;

		TouchPoint(int SidIn, int xIn, int yIn, int zoneIn, String zNameIn,
				int tpointIn) {
			coord = new int[1][6];
			coord[0][0] = xIn;// x coordinate
			coord[0][1] = yIn;// y coordinate
			coord[0][2] = 0;// x velocity
			coord[0][3] = 0;// y velocity
			coord[0][4] = myParent.millis();// time created
			coord[0][5] = zoneIn;// zone containing this point
			SID = SidIn;// SID from TUIO
			zName = zName0 = zNameIn;// zone name
			zone = zone0 = zoneIn;// zone array index
			touchID = myParent.millis() + SID; // TODO this should go away.
			// transform zones
			markZone(tpointIn, zone);// try to take ownership of a zone

		}

		// create a trail of points (history) for this touch point
		void addPoint(int SidIn, int xIn, int yIn, int vxIn, int vyIn,
				int zoneIn, int tpointIn) {
			int tIn = myParent.millis();
			int[] toAppend = { xIn, yIn, vxIn, vyIn, tIn, zoneIn };
			coord = (int[][]) PApplet.append(coord, toAppend);
			SID = SidIn;
			zone = zoneIn;
			modifyZone(xIn, yIn, vxIn, vyIn, tpointIn, zone0);
		}

		// get the last point in trail - return x,y coordinates and velocities
		int[] getLastPoint() {
			int[] xy = { coord[coord.length - 1][0],
					coord[coord.length - 1][1], coord[coord.length - 1][2],
					coord[coord.length - 1][3] };
			return xy;
		}

		// get the first point in trail
		int[] getFirstPoint() {
			int[] xy = { coord[0][0], coord[0][1], coord[0][2], coord[0][3] };
			return xy;
		}
	}

	// /////////////////////////////////////////////////////////////
	// TOUCH ZONE CLASS//////////////////////////////////////////////////
	// /////////////////////////////////////////////////////////////
	class TouchZone {
		int x, y, x0, y0, xi, yi, r, h, w, r0, h0, w0, ri, hi, wi, vx, vy, cx,
				cy, primePoint = -999, friction, touches = 0, releaseTime = 0;
		boolean rectangle, circle, gravity = false, draggable = false,
				scalable = false, hSwipeable = false, throwable = false,
				rotatable = false;
		String name;

		float scl = 1.0f,scli = 1.0f;

		TouchZone(String zNameIn, int xIn, int yIn, int rIn) {
			name = zNameIn;
			// variables followed by 0 are the original values
			// variables followed by i are intermediate values for a historical
			// reference
			x0 = xi = x = xIn;// center of zone x
			y0 = yi = y = yIn;// center of zone y
			r0 = ri = r = rIn;// radius of circle boundary
			w0 = wi = w = 2 * rIn;// width of the square the circle inscribes
			h0 = hi = h = 2 * rIn;// height of the square the circle inscribes
			vx = vy = 0;// velocities
			cx = x;// centerline x
			cy = y;// centerline y
			circle = true;
			rectangle = false;
		}

		TouchZone(String zNameIn, int xIn, int yIn, int wIn, int hIn) {
			name = zNameIn;
			x0 = xi = x = xIn;// upper left corner x
			y0 = yi = y = yIn;// upper left corner y
			w0 = wi = w = wIn;// width
			h0 = hi = h = hIn;// width
			r0 = ri = r = (int) PApplet.sqrt(PApplet.pow(hIn, 2)
					+ PApplet.pow(wIn, 2)) / 2;
			vx = vy = 0;// velocities
			cx = x + w / 2;
			cy = y + h / 2;
			circle = false;
			rectangle = true;
		}
	}

}